/**
 *  at24c08.c - Driver for at24c08 that is a IIC store device.
 *
 *  Copyright (C) 2008-2010 ZhangHu
 *  All rights reserved.
 *  E-MAIL: anmnmnly@gmail.com
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "include/irq.h"
#include "at24cxx.h"
#include "regdef.h"

#define AT24CXX_ADDR 0xA0
#define AT24CXX_SIZE (8 * 1024)


enum IICStatus
{
    WRDATA = 1,
    POLLACK = 2,
    RDDATA = 3,
    SETRDADDR = 4
};

#define IICBUFSIZE 0x20
static uchar_t iicData[IICBUFSIZE];
static volatile int32 iicDataCount;
static volatile int32 iicStatus;
static volatile int32 iicMode;
static int32 iicPt;


static void iic_delay(uint32 delay)
{
    uint32 i = 0, j = 0;

    for(; i<1000; i++) {
        for(; j<delay; j++) {
            ;
        }
    }
}


static void iic_isr(void)
{
    uint32 iicSt = 0, i = 0;
    rI_ISPC = BIT_IIC;

    iicSt = rIICSTAT;
    if(iicSt & 0x8){} // when bus arbitration is failed.
    if(iicSt & 0x4){} // when a slave address is matched with IICADD
    if(iicSt & 0x2){} // when a slave address is 0000000b
    if(iicSt & 0x1){} // when ACK isn't received

    switch(iicMode)
    {
	case POLLACK:
	    iicStatus = iicSt;
	    break;

	case RDDATA:
	    if((iicDataCount--) == 0) {
    		iicData[iicPt++] = (uchar_t)rIICDS;

    		rIICSTAT = 0x90;  // stop MasRx condition
    		rIICCON = 0xAF;   // resumes IIC operation.
    		iic_delay(10);	  // wait until stop condtion is in effect.
    				          // too long time...
    		// The pending bit will not be set after issuing stop condition.
    		break;
	    }
	    iicData[iicPt++] = (uchar_t)rIICDS;
		// The last data has to be read with no ack.
	    if((iicDataCount) == 0) {
		    rIICCON = 0x2F;	  // resumes IIC operation with NOACK.
	    } else {
		    rIICCON=0xAF;	  // resumes IIC operation with ACK
	    }
        break;

	case WRDATA:
	    if((iicDataCount--) == 0)
	    {
    		rIICSTAT = 0xd0;  // stop MasTx condition
    		rIICCON = 0xaf;	  // resumes IIC operation.
    		iic_delay(10);	  // wait until stop condtion is in effect.
    		// The pending bit will not be set after issuing stop condition.
    		break;
	    }
	    rIICDS = iicData[iicPt++]; // _iicData[0] has dummy.
	    iic_delay(10);             // for setup time until rising edge of IICSCL
	    rIICCON = 0xaf;	           // resumes IIC operation.
	    break;

	case SETRDADDR:
	    if((iicDataCount--) == 0) {
		    break;  // IIC operation is stopped because of IICCON[4]
	    }
	    rIICDS = iicData[iicPt++];
	    for(i=0; i<10; i++);  // for setup time until rising edge of IICSCL
	    rIICCON=0xaf;	      // resumes IIC operation.
	    break;

	default:
	    break;
    }
}



void init_iic_eeprom(void)
{
    rPCONF &= 0x3FFF0;
    rPCONF |= 0xA;	  // PF0: IICSCL, PF1: IICSDA
    rPUPF |= 0x3;	  // pull-up disable

    // Enable interrupt, IICCLK=MCLK/16, Enable ACK
    // 40Mhz/16/(15+1) = 257Khz
    rIICCON = (1 << 7) | (0 << 6) | (1 << 5) | (0xf);

    rIICADD = 0x10;   // S3C44B0X slave address
    rIICSTAT = 0x10;  // Initial status.

    isrInstall(IIC_ISR_NUM , 0, iic_isr);
    rINTMSK &= 0x03FFFFDF; // enable IIC interrupt.
}



void set_iic_eeprom_mode(bool_t master_mode)
{

}



uint32 get_iic_eeprom_size(void)
{
    return(AT24CXX_SIZE);
}



extern bool_t iic_eeprom_write_byte(uint32 addr, char_t data)
{
    if(addr >= AT24CXX_SIZE) {
        return(FALSE);
    }

    iicMode = WRDATA;
    iicPt = 0;
    iicData[0] = (uchar_t)addr;
    iicData[1] = data;
    iicDataCount = 2;

    rIICDS = AT24CXX_ADDR;// 0xa0
    rIICSTAT = 0xF0;      // MasTx,Start

    // Clearing the pending bit isn't needed because the pending bit has been cleared.
    while(iicDataCount != -1) {
        ;
    }

    iicMode = POLLACK;

    while(1)
    {
    	rIICDS = AT24CXX_ADDR;
    	iicStatus = 0x100;
    	rIICSTAT = 0xF0; // MasTx,Start
    	rIICCON = 0xAF;  // resumes IIC operation.
    	while(iicStatus == 0x100) {
            ;
    	}

    	if(!(iicStatus & 0x1)) {
    	    break; // when ACK is received
    	}
    }

    rIICSTAT = 0xD0; // stop MasTx condition
    rIICCON = 0xAF;  // resumes IIC operation.
    iic_delay(10);   // wait until stop condtion is in effect.
    return(TRUE);
}



extern char_t iic_eeprom_read_byte(uint32 addr, int32 *err)
{
    if(err != NULL) {
        *err = 1;
    }

    if(addr >= AT24CXX_SIZE) {
        if(err != NULL) {
            *err = 0;
        }

        return(0);
    }

    iicMode = SETRDADDR;
    iicPt = 0;
    iicData[0] = (uchar_t)addr;
    iicDataCount = 1;

    rIICDS = AT24CXX_ADDR;
    rIICSTAT = 0xF0; // MasTx, Start
    // Clearing the pending bit isn't needed
    // because the pending bit has been cleared.
    while(iicDataCount != -1);

    iicMode = RDDATA;

    iicPt = 0;
    iicDataCount = 1;

    rIICDS = AT24CXX_ADDR;
    rIICSTAT = 0xB0;        // MasRx,Start
    rIICCON = 0xAF;         // resumes IIC operation.
    while(iicDataCount != -1) {
        ;
    }

    return(iicData[1]);
}

